package com.sneakxy.mybatis.commons.repository.query.parser;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.reflection.ParamNameResolver;
import org.apache.ibatis.session.Configuration;
import org.springframework.data.domain.Pageable;

/**
 * @author 潜行的青衣
 */
public class MybatisCommonsParamNameResolver extends ParamNameResolver {

	/**
	 * @param config
	 * @param method
	 */
	public MybatisCommonsParamNameResolver(Configuration config, Method method) {
		super(config, method);
		this.onInit(config, method);
	}

	protected void onInit(Configuration config, Method method) {
		final Class<?>[] paramTypes = method.getParameterTypes();
		final Annotation[][] paramAnnotations = method.getParameterAnnotations();
		final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
		int paramCount = paramAnnotations.length;
		try {
			// get names from @Param annotations
			for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
				if ((boolean) MethodUtils.invokeMethod(this, true, "isSpecialParameter", paramTypes[paramIndex])) {
					// skip special parameters
					continue;
				}
				String name = null;
				for (Annotation annotation : paramAnnotations[paramIndex]) {
					if (annotation instanceof org.springframework.data.repository.query.Param) {
						FieldUtils.writeField(this, "hasParamAnnotation", true, true);
						name = ((org.springframework.data.repository.query.Param) annotation).value();
						break;
					}
					if (annotation instanceof Param) {
						FieldUtils.writeField(this, "hasParamAnnotation", true, true);
						name = ((Param) annotation).value();
						break;
					}
				}
				if (name == null) {
					// @Param was not specified.
					if (config.isUseActualParamName()) {
						name = (String) MethodUtils.invokeMethod(this, true, "getActualParamName", method, paramIndex);
					}
					if (name == null) {
						// use the parameter index as the name ("0", "1", ...)
						// gcode issue #71
						name = String.valueOf(map.size());
					}
				}
				map.put(paramIndex, name);
			}
			FieldUtils.writeField(this, "names", Collections.unmodifiableSortedMap(map), true);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public Object getNamedParams(Object[] args) {
		Object parameterObject = super.getNamedParams(args);
		if(!(parameterObject instanceof Map)) {
			final Map<String, Object> param = new ParamMap<Object>();
			param.put(new StringBuilder().append(MybatisPartTree.GENERIC_NAME_PREFIX).append("1").toString(), parameterObject);
			parameterObject = param;
		} else if(args != null && args.length == 1) {
			if(args[0] instanceof Collection || args[0].getClass().isArray()) {
				((Map<String, Object>) parameterObject).put(new StringBuilder().append(MybatisPartTree.GENERIC_NAME_PREFIX).append("1").toString(), args[0]);
			}
		}
		return parameterObject;
	}
	
	@SuppressWarnings("unchecked")
	public Object getNamedParams(Object[] args, boolean isPageCount) {
		Object parameterObject = getNamedParams(args);
		if(isPageCount) {
			Map<String, Object> parameterMap = (Map<String, Object>) parameterObject;
			Iterator<Map.Entry<String, Object>> it = parameterMap.entrySet().iterator();
			while(it.hasNext()) {
				if(it.next().getValue() instanceof Pageable) {
					it.remove();
				}
			}
		}
		return parameterObject;
	}
	
}
